Parser.js ➔ ???   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
nc 1
dl 0
loc 23
rs 9.0856
cc 2
nop 1
1
'use strict';
2
3
import { Lexer, Token } from '../Lexer';
4
5
/**
6
 * The botlang parser for creating the abstract syntax tree.
7
 */
8
class Parser {
9
  /**
10
   * Create a Parser.
11
   * @param {Lexer} lexer
12
   * @throws {TypeError}
13
   */
14
  constructor(lexer) {
15
    if (!(lexer instanceof Lexer)) {
16
      throw new TypeError('Argument "lexer" must be an instance of type "Lexer".');
17
    }
18
19
    /**
20
     * @private
21
     * @type {Lexer}
22
     */
23
    this.lexer = lexer;
24
25
    /**
26
     * @private
27
     * @type {Array}
28
     */
29
    this.syntaxTree = [];
30
31
    /**
32
     * @private
33
     * @type {Integer}
34
     */
35
    this.syntaxTreeIndex = 0;
36
  }
37
38
  /**
39
   * @private
40
   * @return {Object|null}
41
   */
42
  getLastNode() {
43
    return 'undefined' !== typeof this.syntaxTree[this.syntaxTreeIndex - 1]
44
      ? this.syntaxTree[this.syntaxTreeIndex - 1]
45
      : null;
46
  }
47
48
  /**
49
   * @private
50
   * @param  {Token} token
51
   * @return {Boolean}
52
   */
53
  static isResponse(token) {
54
    return token instanceof Token && 'operation' === token.getType() && '-' === token.getValue();
55
  }
56
57
  /**
58
   * @private
59
   * @param  {Token} token
60
   * @return {Boolean}
61
   */
62
  static isTrigger(token) {
63
    return token instanceof Token && 'operation' === token.getType() && '+' === token.getValue();
64
  }
65
66
  /**
67
   * Parse input and return abstract syntax tree (AST)
68
   * @return {Object}
69
   */
70
  parse() {
71
    while (!this.lexer.eof()) {
72
      const token = this.lexer.next();
73
      if (!(token instanceof Token)) {
74
        continue;
75
      }
76
77
      if (Parser.isTrigger(token)) {
78
        this.pushNodeToSyntaxTree(this.parseTrigger());
79
      }
80
81
      if (Parser.isResponse(token) && 'trigger' === this.getLastNode().type) {
82
        this.getLastNode().responses.push(this.parseResponse());
83
      }
84
    }
85
86
    return {
87
      type : 'program',
88
      body : this.syntaxTree
89
    };
90
  }
91
92
  /**
93
   * @private
94
   * @return {Object}
95
   */
96
  parseTrigger() {
97
    const trigger = this.lexer.next();
98
    let pattern = '';
99
100
    if ('string' !== trigger.getType()) {
101
      return this.lexer.inputError('Expected trigger pattern after trigger identifier.');
102
    }
103
104
    pattern = Parser.replaceWildcard(trigger.getValue());
105
    pattern = Parser.replaceStringSubstitution(pattern);
106
107
    return {
108
      type      : 'trigger',
109
      src       : trigger.getValue(),
110
      pattern,
111
      responses : []
112
    };
113
  }
114
115
  /**
116
   * @private
117
   * @return {Object}
118
   */
119
  parseResponse() {
120
    const response = this.lexer.next();
121
    if ('string' !== response.getType()) {
122
      this.lexer.inputError('Expected string after response identifier.');
123
    }
124
125
    return {
126
      type  : 'response',
127
      value : response.getValue()
128
    };
129
  }
130
131
  /**
132
   * @private
133
   * @param  {Object} node
134
   * @return {void}
135
   */
136
  pushNodeToSyntaxTree(node) {
137
    this.syntaxTreeIndex += 1;
138
    this.syntaxTree.push(node);
139
  }
140
141
  /**
142
   * Replace string substitution [$] character
143
   * @param  {String} string
144
   * @return {String}
145
   */
146
  static replaceStringSubstitution(string) {
147
    return string.replace(/(\s?)\$(\s?)/g, '\\s?(\\w+?)\\s?');
148
  }
149
150
  /**
151
   * Replace wildcard [*] character
152
   * @param  {String} string
153
   * @return {String}
154
   */
155
  static replaceWildcard(string) {
156
    return string.replace(/(\s?)\*(\s?)/g, '(\\w?)');
157
  }
158
}
159
160
export default Parser;
161